// patterns/BoxObserver.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Demonstration of Observer pattern using
// Java's built-in observer classes
// {ExcludeFromTravisCI}

import onjava.MouseClick;
import onjava.TimedAbort;

import javax.swing.*;
import java.awt.*;
import java.util.Observable;
import java.util.Observer;

// You must inherit a new type of Observable:
class BoxObservable extends Observable {
    @Override
    public void notifyObservers(Object b) {
        // Otherwise it won't propagate changes:
        setChanged();
        super.notifyObservers(b);
    }
}

public class BoxObserver extends JFrame {
    Observable notifier = new BoxObservable();

    public BoxObserver(int grid) {
        setTitle("Demonstrates Observer pattern");
        Container cp = getContentPane();
        cp.setLayout(new GridLayout(grid, grid));
        for (int x = 0; x < grid; x++)
            for (int y = 0; y < grid; y++)
                cp.add(new OCBox(x, y, notifier));
    }

    public static void main(String[] args) {
        new TimedAbort(4);
        int grid = 8;
        if (args.length > 0)
            grid = Integer.parseInt(args[0]);
        JFrame f = new BoxObserver(grid);
        f.setSize(500, 400);
        f.setVisible(true);
        f.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    }
}

class OCBox extends JPanel implements Observer {
    Observable notifier;
    int x, y; // Locations in grid
    Color cColor = newColor();
    static final Color[] COLORS = {
            Color.black, Color.blue, Color.cyan,
            Color.darkGray, Color.gray, Color.green,
            Color.lightGray, Color.magenta,
            Color.orange, Color.pink, Color.red,
            Color.white, Color.yellow
    };

    static Color newColor() {
        return COLORS[
                (int) (Math.random() * COLORS.length)
                ];
    }

    OCBox(int x, int y, Observable notifier) {
        this.x = x;
        this.y = y;
        notifier.addObserver(this);
        this.notifier = notifier;
        addMouseListener((MouseClick)
                e -> notifier.notifyObservers(OCBox.this));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(cColor);
        Dimension s = getSize();
        g.fillRect(0, 0, s.width, s.height);
    }

    @Override
    public void update(Observable o, Object arg) {
        OCBox clicked = (OCBox) arg;
        if (nextTo(clicked)) {
            cColor = clicked.cColor;
            repaint();
        }
    }

    private boolean nextTo(OCBox b) {
        return Math.abs(x - b.x) <= 1 &&
                Math.abs(y - b.y) <= 1;
    }
}
